package cn.yeziji.forum.utils;

import io.lettuce.core.KeyScanCursor;
import io.lettuce.core.RedisFuture;
import io.lettuce.core.ScanArgs;
import io.lettuce.core.ScanCursor;
import io.lettuce.core.api.async.RedisAsyncCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Redis 工具类
 *
 * @author gzkemays
 * @since 2021/12/22 20:03
 */
@Component
@SuppressWarnings("unchecked")
public class RedisUtils {
  @Resource private RedisTemplate<String, Object> redisTemplate;

  public RedisTemplate<String, Object> getRedisTemplate() {
    return this.redisTemplate;
  }

  public RedisUtils(RedisTemplate<String, Object> redisTemplate) {
    this.redisTemplate = redisTemplate;
  }

  public RedisUtils() {}
  /**
   * 加入 zset
   *
   * @param key 键值
   * @param value 数据
   * @param source source 位置
   */
  public void zsetAdd(String key, Object value, double source) {
    redisTemplate.opsForZSet().add(key, value, source);
  }

  /**
   * 加入 zset 时，source 为最后一个
   *
   * @param key 键值
   * @param value 数据
   */
  public void zsetAddToLast(String key, Object value) {
    List<Object> objects = zsetList(key, Object.class);
    int size = objects.size();
    zsetAdd(key, value, size);
  }

  /**
   * 根据 key source 进行覆盖
   *
   * @param key 键值
   * @param value 数据
   * @param source source 位置
   */
  public void zsetCoverByResource(String key, Object value, long source) {
    /*    Set<Object> range = redisTemplate.opsForZSet().rangeByScore(key, source, source);
    if (range != null && !range.isEmpty()) {
      redisTemplate.opsForZSet().removeRangeByScore(key, source, source);
    }*/
    zsetAdd(key, value, source);
  }

  /**
   * 获取 zset resource member
   *
   * @param key 键值
   * @param source source 值
   * @param clazz 指定泛型类
   * @param <T> 泛型
   * @return 泛型数据
   */
  public <T> T zsetGetBySource(String key, long source, Class<T> clazz) {
    Set<Object> range = redisTemplate.opsForZSet().range(key, source, source);
    if (range != null && !range.isEmpty()) {
      Optional<Object> first = range.stream().findFirst();
      return (T) first.get();
    }
    return null;
  }

  /**
   * 获取 zset 列表
   *
   * @param key 键值
   * @param clazz 指定泛型类
   * @param <T> 泛型
   * @return 泛型列表
   */
  public <T> List<T> zsetList(String key, Class<T> clazz) {
    Set<Object> range = redisTemplate.opsForZSet().range(key, 0, -1);
    if (range != null && !range.isEmpty()) {
      return range.stream().map(data -> (T) data).collect(Collectors.toList());
    }
    return null;
  }

  /**
   * 保存 redis hash
   *
   * @param key 键值
   * @param hashKey hash 键值
   * @param value 保存对象值
   * @param time 过期时间
   * @param timeUnit 过期时间单位
   */
  public void setHash(String key, String hashKey, Object value, long time, TimeUnit timeUnit) {
    redisTemplate.opsForHash().put(key, hashKey, value);
    redisTemplate.expire(key, time, timeUnit);
  }

  public void setHash(String key, String hashKey, Object value) {
    redisTemplate.opsForHash().put(key, hashKey, value);
  }

  /**
   * 保存 redis value
   *
   * @param key 键值
   * @param value 对象值
   * @param time 过期时间
   * @param timeUnit 时间单位
   */
  public void set(String key, Object value, long time, TimeUnit timeUnit) {
    redisTemplate.opsForValue().set(key, value);
    redisTemplate.expire(key, time, timeUnit);
  }

  public void set(String key, Object value) {
    redisTemplate.opsForValue().set(key, value);
  }

  /**
   * 获取剩余时间
   *
   * @param key key 值
   * @param timeUnit 时间单位
   * @return 剩余时长
   */
  public long getKeyExpire(String key, TimeUnit timeUnit) {
    Long expire = redisTemplate.getExpire(key, timeUnit);
    if (expire != null) {
      return expire;
    }
    return -1L;
  }
  /**
   * 获取剩余时间
   *
   * @param key key 值
   * @return 剩余时长
   */
  public long getKeyExpire(String key) {
    Long expire = redisTemplate.getExpire(key);
    if (expire != null) {
      return expire;
    }
    return -1L;
  }

  public void setExpire(String key, long time, TimeUnit timeUnit) {
    redisTemplate.expire(key, time, timeUnit);
  }

  public void setExpireEnd(String key) {
    setExpire(key, 1, TimeUnit.SECONDS);
  }

  /**
   * 获取 hash 对象
   *
   * @param key 键值
   * @param hashKey hash 键值
   * @param clazz 获取对象类
   * @param <T> 泛型对象
   * @return 泛型值
   */
  public <T> T getByHash(String key, String hashKey, Class<T> clazz) {
    return (T) redisTemplate.opsForHash().get(key, hashKey);
  }

  public <T> T getByHash(String key, String hashKey) {
    return (T) redisTemplate.opsForHash().get(key, hashKey);
  }

  public long getHashSize(String key) {
    return redisTemplate.opsForHash().size(key);
  }

  public <T> List<T> getValuesByHashKey(String key) {
    return (List<T>) redisTemplate.opsForHash().values(key);
  }

  public Object getByHashObj(String key, String hashKey) {
    return redisTemplate.opsForHash().get(key, hashKey);
  }

  /**
   * 获取 key value
   *
   * @param key 键值
   * @param clazz 对象类
   * @param <T> 泛型
   * @return 泛型值
   */
  public <T> T get(String key, Class<T> clazz) {
    return (T) redisTemplate.opsForValue().get(key);
  }
  /**
   * 判断key是否存在
   *
   * @param key 键
   * @return true 存在 false 不存在
   */
  public boolean hasKey(String key) {
    try {
      return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    } catch (Exception e) {
      e.printStackTrace();
      return false;
    }
  }

  /**
   * 向list头部添加list数据
   *
   * @param key 键
   * @param value 值
   */
  public Long listHeaderPushAll(String key, Object... value) {
    return redisTemplate.opsForList().leftPushAll(key, value);
  }

  /**
   * 向list末尾添加list数据
   *
   * @param key 键
   * @param value 值
   */
  public Long listFooterPushAll(String key, Object... value) {
    if (value.length > 0) {
      return redisTemplate.opsForList().rightPushAll(key, value);
    }
    return 0L;
  }

  public Long listHeaderRemove(String key, Object value) {
    return listRemove(key, 0, value);
  }

  public Long listFooterRemove(String key, Object value) {
    return listRemove(key, -1, value);
  }

  /**
   * start 大于 0 从左到右删除 value 相同的值
   *
   * @param key 键值
   * @param start 开始位置
   * @param value 删除的值
   */
  public Long listRemove(String key, long start, Object value) {
    return redisTemplate.opsForList().remove(key, start, value);
  }

  /**
   * 通过索引设置list元素的值
   *
   * @param key 键
   * @param index 索引
   * @param value 值
   */
  public void listIndexSet(String key, long index, Object value) {
    redisTemplate.opsForList().set(key, index, value);
  }

  /**
   * 获取列表指定范围内的list元素，正数则表示正向查找，负数则倒叙查找
   *
   * @param key 键
   * @param start 开始
   * @param end 结束
   * @return boolean
   */
  public Object listRange(String key, long start, long end) {
    return redisTemplate.opsForList().range(key, start, end);
  }

  public Object list0Range(String key) {
    return redisTemplate.opsForList().range(key, 0, listLen(key));
  }

  public Object listEndRange(String key) {
    return redisTemplate.opsForList().range(key, -1, listLen(key));
  }

  /**
   * 获取list长度
   *
   * @param key 键
   * @return 列表长度
   */
  public Long listLen(String key) {
    return redisTemplate.opsForList().size(key);
  }
  /**
   * 模糊判断 key 是否存在
   *
   * @param key 键
   * @return 如果存在则直接返回 set，如果为 null 或为空则为不存在。
   */
  public Set<String> blurHasKey(String key) {
    return redisTemplate.execute(
        connection -> {
          // scan 迭代遍历键，返回的结果可能会有重复，需要客户端去重复
          Set<String> redisKeys = new HashSet<>();
          // lettuce 原生api
          RedisAsyncCommands conn = (RedisAsyncCommands) connection.getNativeConnection();
          // 游标
          ScanCursor curs = ScanCursor.INITIAL;
          try {
            // 采用 SCAN 命令，迭代遍历所有key
            while (!curs.isFinished()) {
              long count = 10000L;
              ScanArgs args = ScanArgs.Builder.matches(key).limit(count);
              RedisFuture<KeyScanCursor<byte[]>> future = conn.scan(curs, args);
              KeyScanCursor<byte[]> keyCurs = future.get();
              List<byte[]> ks = keyCurs.getKeys();
              Set<String> set =
                  ks.stream()
                      .map(bytes -> new String(bytes, StandardCharsets.UTF_8))
                      .collect(Collectors.toSet());
              redisKeys.addAll(set);
              curs = keyCurs;
            }
          } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
          }
          return redisKeys;
        },
        true);
    //    return redisTemplate.keys(key + "*");
  }

  public boolean hasHashKey(String key, String hashKey) {
    Object obj = redisTemplate.opsForHash().get(key, hashKey);
    return obj != null;
  }
}
